package com.stox.ioc;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.stox.core.model.exception.StoxException;

public class Container{

	private static final Map<Class<?>,Object> map = new HashMap<Class<?>,Object>();
	private static Class<?>[] classes = new ClassPathScanner().getClassesInPackage("com.stox", null);
	
	public static void addSingleton(Object object){
		map.put(object.getClass(), object);
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getSingleton(Class<T> clazz) {
		T instance;
		try {
			if(!existsSingleton(clazz)){
				instance = instanciateBean(clazz);
				map.put(clazz, instance);
				initializeBean(instance);
			}else{
				instance = (T)map.get(clazz);
			}
		} catch (Exception e) {
			instance = null;
			e.printStackTrace();
		}
		return instance;
	}

	@SuppressWarnings("unchecked")
	public static <T> T getSingleton(Class<T> clazz, Object[] args) {
		T instance;
		try {
			if(!existsSingleton(clazz)){
				instance = instanciateBean(clazz, args);
				map.put(clazz, instance);
				initializeBean(instance);
			}else{
				instance = (T)map.get(clazz);
			}
		} catch (Exception e) {
			instance = null;
			e.printStackTrace();
		}
		return instance;
	}
	
	@SuppressWarnings("unchecked")
	private static <T> T instanciateBean(Class<T> clazz){
		try {
			if(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())){
				for(Class<?> c : classes){
					if( c != null 
							&& !c.isAnnotation()
							&& !c.isArray()
							&& !c.isEnum()
							&& !c.isInterface()
							&& !c.isPrimitive()
							&& !Modifier.isAbstract(c.getModifiers())
							&& clazz.isAssignableFrom(c)){
						return (T)c.newInstance();
					}
				}
			}
			return clazz.newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	private static <T> T instanciateBean(Class<T> clazz, Object[] args){
		try {
			if(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())){
				for(Class<?> c : classes){
					if(c != null 
							&& !c.isAnnotation()
							&& !c.isArray()
							&& !c.isEnum()
							&& !c.isInterface()
							&& !c.isPrimitive()
							&& !Modifier.isAbstract(c.getModifiers())
							&& clazz.isAssignableFrom(c)){
						return (T)c.getConstructor(retrieveTypes(args)).newInstance(args);
					}
				}
			}
			return clazz.getConstructor(retrieveTypes(args)).newInstance(args);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	private static Class<?>[] retrieveTypes(Object[] objects){
		Class<?>[] types = new Class<?>[objects.length];
		for(int i = 0; i < objects.length; i++){
			types[i] = objects[i].getClass();
		}
		return types;
	}
	
	
	
	
	private static void initializeBean(Object object){
		try {
			Method method = object.getClass().getMethod("afterPropertiesSet", new Class<?>[0]);
			if(method != null){
				method.invoke(object, new Object[0]);
			}
		}catch(NoSuchMethodException nsme){
			//ignored
		}catch (Exception e) {
			throw new StoxException(e);
		}
	}
	
	private static <T> T createBean(Class<T> clazz){
		T instance = instanciateBean(clazz);
		initializeBean(instance);
		return instance;
	}
	
	public static boolean existsSingleton(Class<?> clazz){
		return map.containsKey(clazz);	
	}


	
	
	@SuppressWarnings("unchecked")
	public static <T> List<T> getImplementations(Class<T> clazz) {
		List<T> list = new ArrayList<T>();
		for(Class<?> c : classes){
			if(c != null && clazz.isAssignableFrom(c)){
				list.add((T)get(c));
			}
		}
		return list;
	}
	
	public static <T> T getPrototype(Class<T> clazz) {
		return createBean(clazz);
	}
	
	public static <T> T get(Class<T> clazz) {
		return getSingleton(clazz);
	}

	public static <T> T get(Class<T> clazz, Object...args) {
		return getSingleton(clazz, args);
	}
	
}
